NumPy は C で構築されていますが、計算負荷の高いアルゴリズムの一部は ベクトル化の壁にぶつかります。これは、Python の動的性質に起因する固有の遅延が、高レベルな抽象化の利点を上回る場合に起こります。
1. インタプリタのコストとボクシング
標準的な Python ループの各反復では、動的型チェックと参照カウンティングが行われます。NumPy スカラを使っても、生の C データを Python オブジェクトに「ボクシング」することで、$\text{logit}(p) = \log(p/(1-p))$ のような関数において大きなボトルネックが発生します。C でエッジケースを処理するほうが劇的に高速です:
>>> logit(0) → -inf >>> logit(1) → inf >>> logit(2) → nan >>> logit(-2) → nan
2. 中間配列の肥大化
純粋な NumPy 式は、各部分演算ごとに一時的なメモリバッファを作成します。C-API を通じた拡張により、 カーネル融合カーネル融合が可能になり、ロジット変換を一回のパスで計算でき、補助的なメモリオーバーヘッドなしに済みます。
3. 空間的依存関係
隣接要素へのアクセスパターンを含む演算、たとえば 2 次元ステンシル:
$$B(I, J) = A(I, J) + (A(I-1, J) + A(I+1, J) + A(I, J-1) + A(I, J+1)) \cdot 0.5D0 + (A(I-1, J-1) + A(I-1, J+1) + A(I+1, J-1) + A(I+1, J+1)) \cdot 0.25D0$$
スライシングによる表現では、重複したメモリコピーが避けられないため、効率的に実装するのは困難です。C 拡張機能を使用すれば、直接かつキャッシュ整列されたポインタ演算が可能になります。
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>